/*
 * Copyright (c) 2003-2004, Inversoft, All Rights Reserved
 *
 * This software is distribuable under the GNU Lesser General Public License.
 * For more information visit gnu.org.
 */
package org.emergent.antbite.savant.ant.taskdefs;


import java.io.File;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PatternSet;

import org.emergent.antbite.savant.Artifact;
import org.emergent.antbite.savant.ArtifactGroup;
import org.emergent.antbite.savant.Dependencies;
import org.emergent.antbite.savant.DependencyListener;
import org.emergent.antbite.savant.DependencyMediator;
import org.emergent.antbite.savant.LocalProject;
import org.emergent.antbite.savant.SavantException;
import org.emergent.antbite.savant.SavantInternetProcess;
import org.emergent.antbite.savant.Workflow;
import org.emergent.antbite.savant.ant.AntLocalProjectBuilder;
import org.emergent.antbite.savant.ant.AntLogListener;
import org.emergent.antbite.savant.ant.types.ArtifactGroupType;
import org.emergent.antbite.savant.ant.types.ArtifactType;
import org.emergent.antbite.savant.ant.types.LocalProjectType;
import org.emergent.antbite.savant.ant.types.WorkflowType;
import org.emergent.antbite.savant.log.LogManager;


/**
 * <p>
 * This class is an ant task that stores all the necessary
 * information about project dependencies.
 * </p>
 *
 * @author  Brian Pontarelli
 */
public class DependencyTask extends Task {

    /**
     * Since there should only ever be one dependencies defintion, this is the
     * reference ID that the one dependency is stored under.
     */
    public static final String REFERENCE_ID = "com.inversoft.ant.Dependencies";

    private String group;
    private DependencyMediator mediator = new DependencyMediator();
    private Dependencies dependencies = new Dependencies();


    /**
     * Constructs a new <code>DependencyTask</code>.
     */
    public DependencyTask() {
        mediator.setDependencies(dependencies);
    }


    /**
     * Retrieves the group of this project, which is used when publishing the
     * artifacts that this project publishes.
     *
     * @return  The group name
     */
    public String getGroup() {
        return group;
    }

    /**
     * Sets the group of this project, which is used when publishing the artifacts
     * that this project publishes.
     *
     * @param   group The group name
     */
    public void setGroup(String group) {
        this.group = group;
    }

    /**
     * Retrieves the location of the localCache on the file system where dependencies
     * and artifacts are stored.
     *
     * @return  The local cache location
     */
    public File getLocalcache() {
        return mediator.getLocalCacheDir();
    }

    /**
     * Sets the location of the localCache on the file system where the dependencies
     * and artifacts are stored.
     *
     * @param   localCache The local cache location
     */
    public void setLocalcache(File localCache) {
        if (localCache != null) {
            log("Using local cache location of [" + localCache.getAbsolutePath() + "]");
        }

        try {
            mediator.setLocalCacheDir(localCache);
        } catch (SavantException se) {
            log("Invalid local cache location [" + localCache.getAbsolutePath() +
                "]");
            throw new BuildException(se);
        }
    }

    /**
     * Adds a new project dependency to this project.
     *
     * @param   project The project dependency type
     */
    public void addConfiguredProject(LocalProjectType project) {
        if (project.getGroup() == null) {
            if (group == null) {
                throw new BuildException("Either local projects must define" +
                    " a group or the dependency must");
            }

            project.setGroup(group);
        }

        // Setup the builder as an ant builder for this local project
        project.getProxy().setBuilder(new AntLocalProjectBuilder(getProject()));

        try {
            dependencies.addProject(project.getProxy());
            log("Added project [" + project.getProxy().getID() + "]", Project.MSG_DEBUG);
        } catch (SavantException se) {
            log("Invalid local project definition");
            throw new BuildException(se);
        }
    }

    /**
     * Adds a new artifact group to this project.
     *
     * @param   group The new group
     */
    public void addConfiguredArtifactGroup(ArtifactGroupType group) {
        try {
            dependencies.addArtifactGroup(group.getProxy());
        } catch (SavantException se) {
            log("Invalid artifact group definition");
            throw new BuildException(se);
        }
    }

    /**
     * Adds a new artifact to this project.
     *
     * @param   artifact The artifact to add
     */
    public void addConfiguredArtifact(ArtifactType artifact) {
        try {
            dependencies.addArtifact(artifact.getProxy());
        } catch (SavantException se) {
            log("Invalid artifact definition");
            throw new BuildException(se);
        }
    }

    /**
     * Adds a new WorkflowType to this project.
     *
     * @param   workflow The new workflow
     */
    public void addWorkflow(WorkflowType workflow) {
        if (mediator.getWorkflow() != null) {
            throw new BuildException("Only one workflow per project");
        }

        mediator.setWorkflow(workflow.getProxy());
    }

    /**
     * Executes this task that builds up the dependencies and such.
     *
     * @throws  BuildException If the task fails
     */
    public void execute() throws BuildException {
        validate();

        // Setup the default workflow if one was not specified
        if (mediator.getWorkflow() == null) {
            initWorkflow();
        }

        LogManager.getInstance().addListener(new AntLogListener(getProject()));
        
        mediator.addListener(new AntDependencyListener());

        try {
            mediator.mediate();
        } catch (SavantException se) {
            throw new BuildException(se);
        }
    }

    /**
     * Initializes the dependecy task by validating parameters, setting defaults
     * and possibly creating directories.
     */
    protected void validate() {
        if (mediator.getLocalCacheDir() == null) {
            setLocalcache(null);
        }
    }

    /**
     * Sets up the default workflow if one was never set.
     */
    protected void initWorkflow() {
        Workflow workflow = new Workflow();

        Project project = getProject();
        String repositoryDomain = project.getProperty("domain.savant.repository");
        String mappingFile = project.getProperty("file.savant.mapping");
        if (repositoryDomain != null || mappingFile != null) {
            File mapping = new File(mappingFile);
            if (!mapping.exists() || mapping.isDirectory()) {
                mapping = null;
            }

            SavantInternetProcess sip = new SavantInternetProcess();
            sip.setDefaultdomain(repositoryDomain);
            sip.setMapping(mapping);
            workflow.addProcess(sip);
        }

        mediator.setWorkflow(workflow);
    }


    /**
     * The listener class that handles the callbacks from the mediator.
     */
    public class AntDependencyListener implements DependencyListener {
        // Load the defaults
        FileSet defaultFS = findFileSet(ArtifactGroup.DEFAULT_FS);
        Path defaultPath = findClassPath(ArtifactGroup.DEFAULT_PATH);


        /**
         * Handles when a project is built by sending a log message to the user.
         */
        public void projectBuilt(LocalProject project) {
            log("Built project [" + project.getGroup() + ", " + project.getName() + "]");
        }

        /**
         * Handles when an artifact is found by resolving the class path and file
         * set to add the artifact to. If the group passed in is not null and has
         * a file set or class path specified, that is used. Otherwise the defaults
         * are used.
         */
        public void artifactFound(File file, Artifact artifact, ArtifactGroup group) {
            log("Handling artifact [" + artifact + "] group [" + group + "]",
                Project.MSG_DEBUG);

            FileSet fileSet = defaultFS;
            if (group != null) {
                String fileSetId = group.getFilesetid();
                if (fileSetId != null) {
                    fileSet = findFileSet(fileSetId);
                }
            }

            Path classPath = defaultPath;
            if (group != null) {
                String classPathId = group.getClasspathid();
                if (classPathId != null) {
                    classPath = findClassPath(classPathId);
                }
            }

            if (classPath != null) {
                Path.PathElement element = classPath.createPathElement();
                element.setLocation(file);
            }

            if (fileSet != null) {
                // Only add the part of the path that is not the localCache
                // directory path
                PatternSet.NameEntry ne = fileSet.createInclude();
                int length = mediator.getLocalCacheDir().getAbsolutePath().length();
                String artPath = file.getAbsolutePath();
                ne.setName(artPath.substring(length + 1));
            }

            log("Loaded artifact [" + artifact + "]");
        }

        /**
         * Locates the class path with the given id. If it doesn't exists it is created,
         * stored in the project as a reference and then returned.
         *
         * @param   classPathId The ID of the classpath
         * @return  The classpath and never null
         */
        private Path findClassPath(String classPathId) {
            Path path = (Path) getProject().getReference(classPathId);
            if (path == null) {
                path = new Path(getProject());
                getProject().addReference(classPathId, path);
            }

            return path;
        }

        /**
         * Locates the file set with the given id. If it doesn't exists it is created,
         * stored in the project as a reference and then returned.
         *
         * @param   fileSetId The ID of the fileset
         * @return  The fileset and never null
         */
        private FileSet findFileSet(String fileSetId) {
            FileSet fileSet = (FileSet) getProject().getReference(fileSetId);
            if (fileSet == null) {
                fileSet = new FileSet();
                fileSet.setDir(mediator.getLocalCacheDir());
                fileSet.setProject(getProject());
                getProject().addReference(fileSetId, fileSet);
            }

            return fileSet;
        }
    }
}